package com.dotakill.util;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

/**
 * Mybatis动�?�加载Mapper.xml
 *
 * @author houxg
 * @date 2017-4-18
 */
public class MapperRefresh implements InitializingBean, ApplicationContextAware {

	private final HashMap<String, String> mappers = new HashMap<String, String>();
	private volatile ConfigurableApplicationContext context = null;
	private volatile Scanner scanner = null;

	/**
	 * 初始�?
	 */
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.context = (ConfigurableApplicationContext) applicationContext;
	}

	/**
	 * 属�?�被设置�?(�?5秒钟刷新�?�?)
	 */
	public void afterPropertiesSet() throws Exception {
		try {
			scanner = new Scanner();
			new Timer(true).schedule(new TimerTask() {
				public void run() {
					try {
						if (scanner.isChanged()) {
							System.out.println("reloading *mapper.xml");
							scanner.reloadXML();
						}
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			}, 10 * 1000, 5 * 1000);
		} catch (Exception e1) {
			e1.printStackTrace();
		}
	}

	/**
	 * 子类:扫描文件,比对是否发生更改
	 * 
	 */
	@SuppressWarnings("unchecked")
	class Scanner {
		private static final String XML_RESOURCE_PATTERN = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "com/**/*Mapper.xml";
		private final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

		/**
		 * 扫描资源
		 * 
		 * @throws IOException
		 */
		public Scanner() throws IOException {
			Resource[] resources = findResource();
			if (resources != null) {
				for (Resource resource : resources) {
					String key = resource.getURI().toString();
					String value = getMd(resource);
					mappers.put(key, value);
				}
			}
		}

		/**
		 * 重新加载xml
		 * 
		 * @throws Exception
		 */
		public void reloadXML() throws Exception {
			SqlSessionFactory factory = context.getBean(SqlSessionFactory.class);
			Configuration configuration = factory.getConfiguration();
			removeConfig(configuration);
			for (Resource resource : findResource()) {
				try {
					XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(), configuration, resource.toString(), configuration.getSqlFragments());
					xmlMapperBuilder.parse();
				} finally {
					ErrorContext.instance().reset();
				}
			}
		}

		/**
		 * 移除配置
		 * 
		 * @param configuration
		 * @throws Exception
		 */
		private void removeConfig(Configuration configuration) throws Exception {
			Class<?> classConfig = configuration.getClass();
			clearMap(classConfig, configuration, "mappedStatements");
			clearMap(classConfig, configuration, "caches");
			clearMap(classConfig, configuration, "resultMaps");
			clearMap(classConfig, configuration, "parameterMaps");
			clearMap(classConfig, configuration, "keyGenerators");
			clearMap(classConfig, configuration, "sqlFragments");
			clearSet(classConfig, configuration, "loadedResources");
		}

		/**
		 * 清空映射
		 * 
		 * @param classConfig
		 * @param configuration
		 * @param fieldName
		 * @throws Exception
		 */
		private void clearMap(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
			Field field = classConfig.getDeclaredField(fieldName);
			field.setAccessible(true);
			((Map) field.get(configuration)).clear();
		}

		/**
		 * 清空集合
		 * 
		 * @param classConfig
		 * @param configuration
		 * @param fieldName
		 * @throws Exception
		 */
		private void clearSet(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
			Field field = classConfig.getDeclaredField(fieldName);
			field.setAccessible(true);
			((Set) field.get(configuration)).clear();
		}

		/**
		 * 判断文件是否改变
		 * 
		 * @return
		 * @throws IOException
		 */
		public boolean isChanged() throws IOException {
			boolean isChanged = false;
			for (Resource resource : findResource()) {
				String key = resource.getURI().toString();
				String value = getMd(resource);
				if (!value.equals(mappers.get(key))) {
					isChanged = true;
					mappers.put(key, value);
				}
			}
			return isChanged;
		}

		/**
		 * 查找符合条件的资源文�?
		 * 
		 * @return
		 * @throws IOException
		 */
		private Resource[] findResource() throws IOException {
			return resourcePatternResolver.getResources(XML_RESOURCE_PATTERN);
		}

		/**
		 * 获取文件的Md�?(文件长度_�?后修改时�?)
		 * 
		 * @param resource
		 * @return
		 * @throws IOException
		 */
		private String getMd(Resource resource) throws IOException {
			return new StringBuilder().append(resource.contentLength()).append("-").append(resource.lastModified()).toString();
		}
	}
}
